package com.prism.moodme.data;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import com.prism.moodme.util.Utility;

public class Data {
	private String user;
	private int mood;
	private ArrayList<Double> features;
	private String dataID;
	private boolean isCentroid = false;
	public static final String[] conditions = new String[]{"DeepBreathing1",
															"MemorySearch",
															"DeepBreathing2",
															"StroopColorWord",
															"DeepBreathing3",
															"DualTracking",
															"DeepBreathing4",
															"MirrorTrace",
															"DeepBreathing5",
															"SupinePosture",
															"TiltPosture",
															"PublicSpeech",
															"DeepBreathing6"};

	public static final String[] featureNames = new String[]{"SCL_mean",
													   "SCR_STD",
													   "lfPower_RESP",
													   "hfPower_RESP",
													   "AVNN",
													   "lfPower_HRV",
													   "hfPower_HRV",
													   "pnsPower",
													   "snsPower"};

	//timeStamp is of the form: [int month, int day, int hour, int min]
	//Example: example: April 1st at 12 noon will be [4,1,12,0]

	private long time;
	private double gpsLong;
	private double gpsLat;

	/**
	 *
	 * @param userName
	 * @param condition
	 * @param f1
	 * @param f2
	 * @param f3
	 * @param f4
	 * @param f5
	 * @param f6
	 * @param f7
	 * @param f8
	 * @param f9
	 * @param gpsLong
	 * @param gpsLat
	 * @param time
	 */
	public Data(String userName, int condition,
				double f1, double f2, double f3, double f4,
				double f5, double f6, double f7, double f8, double f9,
				double gpsLong, double gpsLat, long time){


		setUser(userName);
		setCondition(condition);

		double[] features = new double[]{f1,f2,f3,f4,f5,f6,f7};
		setFeatures(new ArrayList<Double>());
		for(double curr: features)
			getFeatures().add(curr);

		setGpsLong(gpsLong);
		setGpsLat(gpsLat);
		setTime(time);
		String dataID = getUser();
		for(double curr : getFeatures())
			dataID += curr;

		dataID += getTime();
		setDataID(dataID);
	}

	public Data(String userName, int mood,
			double[] features,
			double gpsLong, double gpsLat, long time){

	setUser(userName);
	setCondition(mood);
	setFeatures(new ArrayList<Double>());
	for(double curr: features)
		getFeatures().add(curr);

	setGpsLong(gpsLong);
	setGpsLat(gpsLat);
	setTime(time);
	String dataID = getUser();
	for(double curr : getFeatures())
		dataID += curr;

	dataID += getTime();
	setDataID(dataID);
}

	public Data(ArrayList<Double> features){
		setUser("CENTROID");
		setFeatures(features);
	}

	private void setFeatures(ArrayList<Double> features) {

		this.features = features;
	}

	public List<Double> getFeatures() {
		return features;
	}

	public double euclid(Data other){
		double euclid = 0.0;
		ArrayList<Double> aa = norm();
		ArrayList<Double> bb = other.norm();

		int len = aa.size();

		for(int i=0; i<len; i++){
			double a = aa.get(i);
			double b = bb.get(i);
			double tot = (a-b)*(a-b);

			euclid += tot;
		}

		return Math.sqrt(euclid);

	}

	public double euclid(ArrayList<Data> others){

		ArrayList<Double> aa = norm();
		double euclids = 0.0;

		int len = aa.size();
		for(Data other : others){
			ArrayList<Double> bb = other.norm();
			double euclid = 0.0;
			for(int i=0; i<len; i++){
				double a = aa.get(i);
				double b = bb.get(i);
				double tot = (a-b)*(a-b);

				euclid += tot;
			}
			euclids += Math.sqrt(euclid);
		}
		return euclids / (double)others.size();
	}

	/**
	 * Returns the cosine similarity between
	 * this feature vector and other
	 * @param other
	 * @return
	 */
	public double cosine(Data other){
		double cos = 0.0;
		int len = size();
		for(int i=0; i<len; i++){
			double a = getFeatures().get(i);
			double b = other.getFeatures().get(i);
			cos += a*b;
		}
		cos = cos/(mag() * other.mag());
		return cos;
	}

	public ArrayList<Double> norm(){
		ArrayList<Double> norm = new ArrayList<Double>();

		for(int i=0; i<size(); i++){
			norm.add(getFeatures().get(i) / mag());
		}

//		System.out.println(norm);
		return norm ;
	}

	/**
	 * Returns magnitude of this feature vector.
	 * @return
	 */
	public double mag(){
		double mag = 0.0;
		int len = size();

		for(int i=0; i<len; i++){
			double a = getFeatures().get(i);
			mag += (a*a);
		}
		return Math.sqrt(mag);
	}

	private void setGpsLong(double gpsLong) {
		this.gpsLong = gpsLong;
	}

	public double getGpsLong() {
		return gpsLong;
	}

	private void setGpsLat(double gpsLat) {
		this.gpsLat = gpsLat;
	}

	public double getGpsLat() {
		return gpsLat;
	}

	public String getFeatureName(int i){
		assert(i < featureNames.length);
		return featureNames[i];
	}

	private void setTime(long time) {
		this.time = time;
	}

	public long getTime() {
		return time;
	}

	public void setCondition(int condition) {
		this.mood = condition;
	}

	public int getCondition() {
		return mood;
	}

	private void setUser(String user) {
		this.user = user;
	}

	public String getUser() {
		return user;
	}
/**
 *
 * @param dataID
 * This function needs to be called when constructing the Data object after retrieving from Database.
 * Thus it should be public.
 */
	public void setDataID(String dataID) {
		this.dataID = dataID;
	}

	public String getDataID() {
		return dataID;
	}

	public double getFeature(int feature){
		return getFeatures().get(feature);
	}
	public String toString(){
		StringBuilder sb = new StringBuilder();
		sb.append(getDataID()+"\t");
		sb.append(getUser()+"\t");
		sb.append(getCondition()+"\t");
		for(double curr : getFeatures())
			sb.append(curr+"\t");
		sb.append(getGpsLong()+"\t");
		sb.append(getGpsLat()+"\t");
		sb.append(getTime());

		return sb.toString();
	}

	public int size(){
		return getFeatures().size();
	}


	public boolean equals(Data other){

		if(getDataID().equalsIgnoreCase(other.getDataID()))
			return true;
		return false;
	}

	public void setCentroid(boolean isCentroid) {
		this.isCentroid = isCentroid;
	}

	public boolean isCentroid() {
		return isCentroid;
	}

	public Calendar getCalendar(){
		return Utility.getTime(getTime());
	}
}

